#include "game_opengl.h"
#include "game_render_group.h"
#include "game_sort.h"
#include "game_shared.h"
#include "game_config.h"

GLuint OpenGLDefaultInternalTextureFormat;
gl_Bind_Framebuffer *glBindFramebuffer;
gl_Delete_Framebuffers *glDeleteFramebuffers;
gl_Gen_Framebuffers *glGenFramebuffers;
gl_Framebuffer_Texture_2D *glFramebufferTexture2D;
gl_Check_Framebuffer_Status *glCheckFramebufferStatus;

void OpenGLSetScreenspace(int32 Width, int32 Height) {
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    real32 a = SafeRatio1(2.0f, (real32)Width);
    real32 b = SafeRatio1(2.0f, (real32)Height);
    real32 Proj[] = {a, 0.0f, 0.0f, 0.0f,
                     //
                     0.0f, b, 0.0f, 0.0f,
                     //
                     0.0f, 0.0f, 1.0f, 0.0f,
                     //
                     -1.0f, -1.0f, 0.0f, 1.0f};
    glLoadMatrixf(Proj);
}

inline void OpenGLRectangle(v2 MinP, v2 MaxP, v4 Color, v2 MinUV = v2{0, 0}, v2 MaxUV = v2{1, 1}) {
    //
    glColor4f(Color.r, Color.g, Color.b, Color.a);
    // 下三角
    glBegin(GL_TRIANGLES);
    glTexCoord2f(MinUV.x, MinUV.y);
    glVertex2f(MinP.x, MinP.y);

    glTexCoord2f(MaxUV.x, MinUV.y);
    glVertex2f(MaxP.x, MinP.y);

    glTexCoord2f(MaxUV.x, MaxUV.y);
    glVertex2f(MaxP.x, MaxP.y);

    // 上三角
    glTexCoord2f(MinUV.x, MinUV.y);
    glVertex2f(MinP.x, MinP.y);

    glTexCoord2f(MaxUV.x, MaxUV.y);
    glVertex2f(MaxP.x, MaxP.y);

    glTexCoord2f(MinUV.x, MaxUV.y);
    glVertex2f(MinP.x, MaxP.y);

    glEnd();
}
GLuint GlobalBlitTextureHandle;
void OpenGLDisplayBitmap(int32 Width, int32 Height, void *Memory, int32 Pitch,
                         rectangle2i DrawRegion, v4 ClearColor, GLuint BlitTexture) {
    OpenGLBindFramebuffer(0, DrawRegion);
    glDisable(GL_SCISSOR_TEST);
    glDisable(GL_BLEND);
    glBindTexture(GL_TEXTURE_2D, BlitTexture);
    // 设置每行实际像素数，支持任意 Pitch
    glPixelStorei(GL_UNPACK_ROW_LENGTH, Pitch / 4);  // 4 = 每像素字节数 (BGRA)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, Width, Height, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE,
                 Memory);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    /*
    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL);
    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC);
    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE);
    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER);
    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER);
    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD);
    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD);
    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL);

    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R);
    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G);
    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B);
    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A);

    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S);
    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T);
    glBindTexture(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R);
    */

    glEnable(GL_TEXTURE_2D);
    glClearColor(ClearColor.r, ClearColor.g, ClearColor.b, ClearColor.a);

    glClear(GL_COLOR_BUFFER_BIT);
    OpenGLSetScreenspace(Width, Height);

    v4 Color = {1, 1, 1, 1};
    v2 MinP = {0, 0};
    v2 MaxP = {(real32)Width, (real32)Height};
    glColor4f(Color.r, Color.g, Color.b, Color.a);

    OpenGLRectangle(MinP, MaxP, Color);
    glBindTexture(GL_TEXTURE_2D, 0);
    glEnable(GL_BLEND);
}
void OPenGLLineVertices(v2 MinP, v2 MaxP) {
    glVertex2f(MinP.x, MinP.y);
    glVertex2f(MaxP.x, MinP.y);

    glVertex2f(MaxP.x, MinP.y);
    glVertex2f(MaxP.x, MaxP.y);

    glVertex2f(MaxP.x, MaxP.y);
    glVertex2f(MinP.x, MaxP.y);

    glVertex2f(MinP.x, MaxP.y);
    glVertex2f(MinP.x, MinP.y);
}
void OpenGLDrawBoundsRecursive(sort_sprite_bound *Bounds, uint32 BoundIndex) {
    sort_sprite_bound *Bound = Bounds + BoundIndex;
    if (!(Bound->Flags & Sprite_DebugBox)) {
        v2 Center = GetCenter(Bound->ScreenArea);
        Bound->Flags |= Sprite_DebugBox;
        for (sprite_edge *Edge = Bound->FirstEdgeWithMeAsFront; Edge;
             Edge = Edge->NextEdgeWithSameFront) {
            sort_sprite_bound *Behind = Bounds + Edge->Behind;
            v2 BehindCenter = GetCenter(Behind->ScreenArea);
            glVertex2fv(Center.E);
            glVertex2fv(BehindCenter.E);
            OpenGLDrawBoundsRecursive(Bounds, Edge->Behind);
        }
        v2 SMin = GetMinCorner(Bound->ScreenArea);
        v2 SMax = GetMaxCorner(Bound->ScreenArea);
        OPenGLLineVertices(SMin, SMax);
    }
}

void *OpenGLAllocateTexture(uint32 Width, uint32 Height, void *Data) {
    GLuint Handle = 0;
    glGenTextures(1, &Handle);
    glBindTexture(GL_TEXTURE_2D, Handle);
    glTexImage2D(GL_TEXTURE_2D, 0, OpenGLDefaultInternalTextureFormat, Width, Height, 0,
                 GL_BGRA_EXT, GL_UNSIGNED_BYTE, Data);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glBindTexture(GL_TEXTURE_2D, 0);
    static_assert(sizeof(Handle) <= sizeof(void *));
    void *Result = Uint32ToPointer(void, Handle);
    return Result;
}

global_variable uint32 TextureBindCount = 0;
global_variable uint32 GlobalFrameBufferCount = 1;
global_variable GLuint GlobalFramebufferHandles[256] = {0};
global_variable GLuint GlobalFramebufferTextures[256] = {0};

void OpenGLBindFramebuffer(uint32 TargetIndex, rectangle2i DrawRegion) {
    uint32 WindowWidth = GetWidth(DrawRegion);
    uint32 WindowHeight = GetHeight(DrawRegion);
    glBindFramebuffer(GL_FRAMEBUFFER, GlobalFramebufferHandles[TargetIndex]);
    if (TargetIndex > 0) {
        glViewport(0, 0, WindowWidth, WindowHeight);
    } else {
        glViewport(DrawRegion.MinX, DrawRegion.MinY, WindowWidth, WindowHeight);
    }
}
void OpenGLRenderCommands(game_render_commands *Commands, game_render_prep *Prep,
                          rectangle2i DrawRegion, uint32 WindowWidth, uint32 WindowHeight) {
    TIMED_FUNCTION();

    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glEnable(GL_SCISSOR_TEST);
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    glLoadIdentity();
    bool32 UseRenderTargets = (glBindFramebuffer != 0);
    uint32 MaxRenderTargetIndex = UseRenderTargets ? Commands->MaxRenderTargetIndex : 0;
    if (MaxRenderTargetIndex >= GlobalFrameBufferCount) {
        uint32 NewFramebufferCount = Commands->MaxRenderTargetIndex + 1;
        Assert(NewFramebufferCount < ArrayCount(GlobalFramebufferHandles));
        glGenFramebuffers(NewFramebufferCount - GlobalFrameBufferCount,
                          GlobalFramebufferHandles + GlobalFrameBufferCount);
        uint32 NewCount = NewFramebufferCount - GlobalFrameBufferCount;
        glGenTextures(NewCount, GlobalFramebufferTextures + GlobalFrameBufferCount);
        for (uint32 TargetIndex = GlobalFrameBufferCount; TargetIndex <= MaxRenderTargetIndex;
             ++TargetIndex) {
            GLuint TextureHandle = PointerToUint32(
                OpenGLAllocateTexture(GetWidth(DrawRegion), GetHeight(DrawRegion), 0));
            GlobalFramebufferTextures[TargetIndex] = TextureHandle;
            OpenGLBindFramebuffer(TargetIndex, DrawRegion);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                   TextureHandle, 0);
            GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
            Assert(Status == GL_FRAMEBUFFER_COMPLETE);
        }
        GlobalFrameBufferCount = NewFramebufferCount;
    }
    for (uint32 TargetIndex = 0; TargetIndex <= MaxRenderTargetIndex; ++TargetIndex) {
        if (UseRenderTargets) {
            OpenGLBindFramebuffer(TargetIndex, DrawRegion);
        }
        if (TargetIndex == 0) {
            glScissor(0, 0, WindowWidth, WindowHeight);
        } else {
            glScissor(0, 0, GetWidth(DrawRegion), GetHeight(DrawRegion));
        }
        glClearColor(Commands->ClearColor.r, Commands->ClearColor.g, Commands->ClearColor.b,
                     Commands->ClearColor.a);
        glClear(GL_COLOR_BUFFER_BIT);
    }
    OpenGLSetScreenspace(Commands->Width, Commands->Height);
    uint32 ClipRectIndex = 0xFFFFFFFF;
    uint32 CurrentRenderTargetIndex = 0xFFFFFFFF;
    uint32 *SortEntry = Prep->SortedIndices;

    for (uint32 SortEntryIndex = 0; SortEntryIndex < Prep->SortedIndexCount;
         ++SortEntryIndex, ++SortEntry) {
        uint32 HeadOffset = *SortEntry;
        render_group_entry_header *Header =
            (render_group_entry_header *)(Commands->PushBufferBase + HeadOffset);
        if (UseRenderTargets ||
            (Prep->ClipRects[Header->ClipRectIndex].RenderTargetIndex <= MaxRenderTargetIndex)) {
            if (ClipRectIndex != Header->ClipRectIndex) {
                ClipRectIndex = Header->ClipRectIndex;
                Assert(ClipRectIndex < Commands->ClipRectCount);
                render_entry_cliprect *Clip = Prep->ClipRects + ClipRectIndex;
                rectangle2i ClipRect = Clip->Rect;
                if (!UseRenderTargets || (Clip->RenderTargetIndex == 0)) {
                    ClipRect = Offset(ClipRect, DrawRegion.MinX, DrawRegion.MinY);
                }
                if (CurrentRenderTargetIndex != Clip->RenderTargetIndex) {
                    CurrentRenderTargetIndex = Clip->RenderTargetIndex;
                    Assert(CurrentRenderTargetIndex <= MaxRenderTargetIndex);
                    if (UseRenderTargets) {
                        OpenGLBindFramebuffer(CurrentRenderTargetIndex, DrawRegion);
                    }
                }
                glScissor(ClipRect.MinX, ClipRect.MinY,   //
                          ClipRect.MaxX - ClipRect.MinX,  //
                          ClipRect.MaxY - ClipRect.MinY);
            }
            void *Data = (uint8 *)Header + sizeof(*Header);
            if (Header->DebugTag == 1) {
                int BreakHere = true;
                (void)BreakHere;
            }
            // 根据条目的类型进行不同的处理
            switch (Header->Type) {
                // 处理渲染组条目类型为饱和度调整的情况
                case RenderGroupEntryType_render_entry_saturation: {
                    // 将数据指针转换为饱和度渲染条目类型
                    // render_entry_saturation *Entry = (render_entry_saturation *)Data;

                    // 调用 ChangeSaturation 函数来调整输出目标的饱和度
                    // ChangeSaturation(OutputTarget, Entry->Level);
                }

                case RenderGroupEntryType_render_entry_bitmap: {
                    // 处理位图渲染条目
                    render_entry_bitmap *Entry = (render_entry_bitmap *)Data;

                    Assert(Entry->Bitmap);
#if 0
thread_local static std::atomic<real32> Angle = 1.0f;
Angle += 0.0000001f;
v2 XAxis = {Cos(Angle), Sin(Angle)};

#endif
                    if (Entry->Bitmap->Width && Entry->Bitmap->Height) {
                        v2 XAxis = Entry->XAxis;
                        v2 YAxis = Entry->YAxis;
                        v2 MinP = Entry->P;
                        v4 Color = Entry->PremulColor;
                        real32 OneTexelU = 1.0f / Entry->Bitmap->Width;
                        real32 OneTexelV = 1.0f / Entry->Bitmap->Height;
                        v2 MinUV = v2{OneTexelU, OneTexelV};
                        v2 MaxUV = v2{1.0f - OneTexelU, 1.0f - OneTexelV};
                        glBindTexture(GL_TEXTURE_2D, PointerToUint32(Entry->Bitmap->TextureHandle));

                        //
                        glColor4f(Color.r, Color.g, Color.b, Color.a);
                        // 下三角
                        glBegin(GL_TRIANGLES);
                        v2 MinXMinY = MinP;
                        v2 MinXMaxY = MinP + YAxis;
                        v2 MaxXMinY = MinP + XAxis;
                        v2 MaxXMaxY = MinP + XAxis + YAxis;
                        glTexCoord2f(MinUV.x, MinUV.y);
                        glVertex2fv(MinXMinY.E);

                        glTexCoord2f(MaxUV.x, MinUV.y);
                        glVertex2fv(MaxXMinY.E);

                        glTexCoord2f(MaxUV.x, MaxUV.y);
                        glVertex2fv(MaxXMaxY.E);

                        // 上三角
                        glTexCoord2f(MinUV.x, MinUV.y);
                        glVertex2fv(MinXMinY.E);

                        glTexCoord2f(MaxUV.x, MaxUV.y);
                        glVertex2fv(MaxXMaxY.E);

                        glTexCoord2f(MinUV.x, MaxUV.y);
                        glVertex2fv(MinXMaxY.E);

                        glEnd();
                    }
                } break;

                case RenderGroupEntryType_render_entry_rectangle: {
                    // 处理矩形渲染条目
                    render_entry_rectangle *Entry = (render_entry_rectangle *)Data;
                    v2 MinP = Entry->P;
                    v2 MaxP = Entry->P + Entry->Dim;
                    v4 Color = Entry->PremulColor;
                    glDisable(GL_TEXTURE_2D);
                    OpenGLRectangle(MinP, MaxP, Color);
                    glBegin(GL_LINES);
                    glColor4f(0.0f, 0.0f, 0.0f, Entry->PremulColor.a);
                    OPenGLLineVertices(MinP, MaxP);
                    glEnd();
                    glEnable(GL_TEXTURE_2D);
                } break;
                case RenderGroupEntryType_render_entry_cliprect: {
                } break;

                case RenderGroupEntryType_render_entry_coordinate_system: {
                    // 处理坐标系渲染条目
                    render_entry_coordinate_system *Entry = (render_entry_coordinate_system *)Data;
                    (void)Entry;
                } break;
                case RenderGroupEntryType_render_entry_blend_render_target: {
                    render_entry_blend_render_target *Entry =
                        (render_entry_blend_render_target *)Data;
                    if (UseRenderTargets) {
                        glBindTexture(GL_TEXTURE_2D,
                                      GlobalFramebufferTextures[Entry->SourceTargetIndex]);
                        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                        OpenGLRectangle(v2i(0, 0), v2i(Commands->Width, Commands->Height),
                                        V4(1, 1, 1, Entry->Alpha));
                        glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
                    }
                    // loaded_bitmap *SourceTarget = RenderTargets + Entry->SourceTargetIndex;
                    // BlendRenderTarget(ClipRect, OutputTarget, Entry->Alpha, SourceTarget);
                } break;

                    InvalidDefaultCase;
            }
        }
    }

    if (UseRenderTargets) {
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
    if (Global_ShowShortGroups) {
        glDisable(GL_TEXTURE_2D);
        uint32 BoundCount = Commands->SortEntryCount;
        sort_sprite_bound *Bounds = GetSortEntries(Commands);
        uint32 GroupIndex = 0;
        for (uint32 BoundIndex = 0; BoundIndex < BoundCount; ++BoundIndex) {
            sort_sprite_bound *Bound = Bounds + BoundIndex;
            if ((Bounds->Offset != SPRITE_BARRIER_OFFSET_VALUE) &&
                !(Bound->Flags & Sprite_DebugBox)) {
                v4 Color = v4{DebugColorTable[GroupIndex++ % ArrayCount(DebugColorTable)], 0.1f};
                if (Bound->Flags & Sprite_Cycle) {
                    Color.a = 1.0f;
                }
                Color.rgb *= Color.a;
                glColor4fv(Color.E);
                glBegin(GL_LINES);
                OpenGLDrawBoundsRecursive(Bounds, BoundIndex);
                glEnd();
                ++GroupIndex;
            }
        }
    }
}

opengl_info OpenGLGetInfo(bool32 ModernContext) {
    //
    opengl_info Result = {};
    Result.Vendor = (char *)glGetString(GL_VENDOR);
    Result.Renderer = (char *)glGetString(GL_RENDERER);
    Result.Version = (char *)glGetString(GL_VERSION);
    Result.ModernContext = ModernContext;
    if (Result.ModernContext) {
        Result.ShadingLanguageVersion = (char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
    } else {
        Result.ShadingLanguageVersion = "(none)";
    }
    Result.Extensions = (char *)glGetString(GL_EXTENSIONS);
    const char *At = Result.Extensions;
    while (*At) {
        while (IsWhitespace(*At)) {
            At++;
        }
        const char *End = At;
        while (*End && !IsWhitespace(*End)) {
            ++End;
        }
        uintptr_t Count = End - At;
        if (0) {
        } else if (StringsAreEqual(Count, At, "GL_EXT_texture_sRGB_decode")) {
            Result.GL_EXT_texture_sRGB_decode = true;
        } else if (StringsAreEqual(Count, At, "GL_ARB_framebuffer_sRGB")) {
            Result.GL_ARB_framebuffer_sRGB = true;
        } else if (StringsAreEqual(Count, At, "GL_ARB_framebuffer_object")) {
            Result.GL_ARB_framebuffer_object = true;
        }
        At = End;
    }
    char *MajorAt = Result.Version;
    char *MinorAt = 0;
    for (char *VerAt = Result.Version; *VerAt; ++VerAt) {
        if (VerAt[0] == '.') {
            MinorAt = VerAt + 1;
            break;
        }
    }
    int32 Major = 1;
    int32 Minor = 0;
    if (MinorAt) {
        Major = Int32FromZ(MajorAt);
        Minor = Int32FromZ(MinorAt);
    }
    if ((Major >= 3) || ((Major == 2) && (Minor >= 1))) {
        Result.GL_EXT_texture_sRGB_decode = true;
    }
    return Result;
}

opengl_info OpenGLInit(bool32 ModernContext, bool32 FrameBufferSupportsSRGB) {
    opengl_info Info = OpenGLGetInfo(ModernContext);
    OpenGLDefaultInternalTextureFormat = GL_RGBA8;
    if (FrameBufferSupportsSRGB && Info.GL_EXT_texture_sRGB_decode &&
        Info.GL_ARB_framebuffer_sRGB) {  //
        OpenGLDefaultInternalTextureFormat = GL_SRGB8_ALPHA8;
        glEnable(GL_FRAMEBUFFER_SRGB);
    }
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    return Info;
}
void OpenGLManageTexture(texture_op *First) {
    for (texture_op *Op = First; Op; Op = Op->Next) {
        if (Op->IsAllocate) {
            *Op->Allocate.ResultHandle =
                OpenGLAllocateTexture(Op->Allocate.Width, Op->Allocate.Height, Op->Allocate.Data);
        } else {
            GLuint Handle = PointerToUint32(Op->Deallocate.Handle);
            glDeleteTextures(1, &Handle);
        }
    }
}